﻿#region ... Copyright Notice ...
/*
   Copyright 2008 Tyler Jensen

	Author: Tyler Jensen    http://www.tsjensen.com/blog

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Text;
using Atrax.Contracts;
using Atrax.Library;
using Atrax.Consumers;
using Atrax.Security;

namespace Atrax.Dispatcher
{
	public class RequestHandler
	{
		public QueryResponse Execute(QueryRequest request)
		{
			QueryResponse response = new QueryResponse();
			response.QueryId = Guid.NewGuid().ToString();

			//1. basic validation of data in the request
			if (!IsCompleteData(request, ref response))
			{
				LogInvalidRequest(request, response);
				return response;
			}

			//2. validate security and if valid, create authentication token
			string authenticationToken = string.Empty;
			if (!IsValidCredentials(request, ref authenticationToken, ref response))
			{
				LogInvalidCredentials(request, response);
				return response;
			}

			//3. create query with authentication token and call Execute on master
			Query query = new Query() 
			{ 
				Header = new QueryHeader() 
				{  
					LicenseKey = request.LicenseKey,
					AuthenticationToken = authenticationToken, 
					QueryId = response.QueryId, 
					ClientQueryId = request.ClientQueryId, 
					QueryType = request.QueryType, 
					CallbackUrl = request.CallbackUrl, 
					NotificationEmail = request.NotificationEmail, 
					Priority = request.Priority, 
					ProcessingAttempts = 0
				}, 
				Request = request.Query 
			};
			MasterClient master = null;
			try
			{
				master = MasterClientFactory.Create();
				if (query.Request.ToLower() == "sample")
				{
					response = new QueryResponse() 
					{ 
						QueryId = "sample", 
						EstimatedDeliverySeconds = 0.0, 
						SampleRequest = master.GetSampleRequest(query), 
						StatusCode = "200", 
						StatusDescription = "OK", 
						ValidationResult = new QueryValidationResult() 
						{ 
							IsValid = true, 
							ValidationReport = string.Empty 
						} 
					};
				}
				else
				{
					response = master.Execute(query);
				}
				master.Close();
			}
			catch
			{
				if (master != null) master.Abort();
				throw;
			}
			LogMasterResponse(request, response);
			return response;
		}

		private bool IsCompleteData(QueryRequest request, ref QueryResponse response)
		{
			if (request.LicenseKey.Length == 29)
			{
				if (IsValidTimeStamp(request.ClientTimeStamp))
				{
					if (request.AuthorizationCode.Length > 5)
					{
						if (IsValidUrl(request.CallbackUrl))
						{
							if (IsSupportedQueryType(request.QueryType))
							{
								if (request.Query.Length > 0)
								{
									return true;
								}
								else
								{
									response.StatusCode = "523";
									response.StatusDescription = "Invalid or empty query data.";
									return false;
								}
							}
							else
							{
								response.StatusCode = "521";
								response.StatusDescription = "Unsupported query type.";
								return false;
							}
						}
						else
						{
							response.StatusCode = "519";
							response.StatusDescription = "Invalid callback URL.";
							return false;
						}
					}
					else
					{
						response.StatusCode = "517";
						response.StatusDescription = "Invalid authorization code.";
						return false;
					}
				}
				else
				{
					response.StatusCode = "515";
					response.StatusDescription = "Invalid time stamp.";
					return false;
				}
			}
			else
			{
				response.StatusCode = "511";
				response.StatusDescription = "Invalid license key.";
				return false;
			}
		}

		private bool IsValidTimeStamp(string timeStamp)
		{
			//yyyy-MM-dd HH:mm:ss	UTC time
			DateTime dt = DateTime.MinValue;
			if (DateTime.TryParseExact(timeStamp, "yyyy-MM-dd HH:mm:ss", new DateTimeFormatInfo(), DateTimeStyles.None, out dt))
			{
				TimeSpan ts = DateTime.UtcNow - dt;
				if (Math.Abs(ts.TotalMinutes) > 20)  //if more than 20 minutes off utc time, do not validate
					return false;
				else
					return true;
			}
			else
				return false;
		}

		static List<string> supportedQueryTypes = null;
		private bool IsSupportedQueryType(string type)
		{
			if (supportedQueryTypes == null)
			{
				supportedQueryTypes = new List<string>(ConfigurationManager.AppSettings["supportedQueryTypes"].ToLower().Split(','));
			}
			return supportedQueryTypes.Contains(type.ToLower());
		}

		private bool IsValidUrl(string url)
		{
			try
			{
				Uri u = new Uri(url);
				return u.IsAbsoluteUri;
			}
			catch
			{
				//do nothing if error occurs
			}
			return false;
		}

		private bool IsValidCredentials(QueryRequest request, ref string authenticationToken, ref QueryResponse response)
		{
			KeyManager km = new KeyManager();
			if (km.IsValid(request.LicenseKey, request.ClientTimeStamp, request.AuthorizationCode))
			{
				authenticationToken = km.GetAuthenticationToken(request.LicenseKey, request.ClientTimeStamp);
				return true;
			}
			else
			{
				response.StatusCode = "513";
				response.StatusDescription = "Invalid license key or authorization code.";
				return false;
			}
		}

		private void LogInvalidRequest(QueryRequest request, QueryResponse response)
		{
			//todo: log some data for debug / auditing / accounting
		}

		private void LogInvalidCredentials(QueryRequest request, QueryResponse response)
		{
			//todo: log some data for debug / auditing / accounting
		}

		private void LogMasterResponse(QueryRequest request, QueryResponse response)
		{
			//todo: log some data for debug / auditing / accounting
		}
	}
}
